﻿//     Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace Microsoft.Gateway.Tracing
{
    public class TracingHook : IGatewayModuleInit
    {
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

        private const string TotalResponseTime = "GW-TOTAL-RESPONSETIME";
        private const string ARROverhead = "GW-ARR-OVERHEAD";
        private const string GatewayOverhead = "GW-TOTAL-OVERHEAD";
        private const string ExternalResponseTime = "GW-EXTERNAL-RESPONSETIME";
        private const string CorrelationId = "GW-CORRELATION-ID";

        public void Init(System.Web.HttpApplication context)
        {
            context.BeginRequest += OnBeginRequest;
            context.EndRequest += OnEndRequest;
            context.MapRequestHandler += OnMapRequestHandler;
            context.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
            context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
        }

        void OnPostRequestHandlerExecute(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;
            UpdateServiceVariable(context, ExternalResponseTime);
        }

        void OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;
            
            var correlationId = context.Request.Headers[CorrelationId];
            if (string.IsNullOrEmpty(correlationId))
            {
                correlationId = Guid.NewGuid().ToString("D");
            }
            context.Response.Headers.Set(CorrelationId, correlationId);

            RecordServiceVariable(context, ExternalResponseTime);
            UpdateServiceVariable(context, ARROverhead);
        }

        void OnMapRequestHandler(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;
            RecordServiceVariable(context, ARROverhead);
        }

        void OnEndRequest(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;

            UpdateServiceVariable(context, TotalResponseTime);
            double totalResponseTime = ReadServiceVariable(context, TotalResponseTime);
            double externalResponseTime = ReadServiceVariable(context, ExternalResponseTime);
            WriteServiceVariable(context, GatewayOverhead, totalResponseTime - externalResponseTime);
        }

        void OnBeginRequest(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;

            var correlationId = context.Request.Headers[CorrelationId];
            if (string.IsNullOrEmpty(correlationId))
            {
                correlationId = Guid.NewGuid().ToString("D");
                context.Request.Headers.Set(CorrelationId, correlationId);
            }

            RecordServiceVariable(context, TotalResponseTime);
        }

        void RecordServiceVariable(HttpContext context, string variableName)
        {
            double timestamp = QueryPerformanceCounter() * 1000;
            context.Request.ServerVariables.Set(variableName, timestamp.ToString());
        }

        void UpdateServiceVariable(HttpContext context, string variableName)
        {
            double timestamp = QueryPerformanceCounter() * 1000;
            double oldTimestamp = ReadServiceVariable(context, variableName);
            WriteServiceVariable(context, variableName, timestamp - oldTimestamp);
        }

        void WriteServiceVariable(HttpContext context, string variableName, double value)
        {
            context.Request.ServerVariables.Set(variableName, value.ToString());
        }

        double ReadServiceVariable(HttpContext context, string variableName)
        {
            var stringValue = context.Request.ServerVariables[variableName];
            double returnValue = 0;
            if (!string.IsNullOrEmpty(stringValue) && double.TryParse(stringValue, out returnValue))
                return returnValue;
            else
                return 0;
        }

        double QueryPerformanceCounter()
        {
            double result = 0.0;
            long frequency;
            long value;

            if (QueryPerformanceFrequency(out frequency) && frequency > 0)
            {
                QueryPerformanceCounter(out value);
                result = value * 1.0 / frequency;
            }

            return result;
        }
    }
}

